#include "stdafx.h"
#include "XHeaderImpl.h"

#include "Util.h"
#include "DrawUtil.h"
#include "UIUtil.h"
#include "SkinObjMgr.h"
#include "DrawObjHelper.h"
#include "XAppData.h"
#include <IntSkinImpl.h>
#include <UIDefaultValue.h>
#include <TimerUtil.h>

#include "Res/resource.h"

static const int g_nTriMargin = 8;
static const int g_nSplitMargin = 10;
static const int g_nItemBorderWidth = 1;
static const int g_nDefaultItemMinWidth = 20;
static const int g_nDefaultDrawFlags = DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE;

//////////////////////////////////////////////////////////////////////////

template <class I>
CXHeaderImpl<I>::CXHeaderImpl()
{
    m_hSplitCursor = NULL;
    m_nDragIndex = -1;
    m_nOrgX = 0;
    m_bSizeChanging = FALSE;

    m_nSortIndex = -1;
    m_nClickedIndex = -1;
    m_bSortAsc = TRUE;

    m_pArrow = NULL;
    m_uWidthChangedTimerId = g_nInvalidTimerId;

    ModifyControlFlags(ESkin_CanHover, TRUE);
    ModifyControlFlags(ESkin_NewCursor, TRUE);
    ModifyControlFlags(ESkin_Interactive, TRUE);
}

template <class I>
CXHeaderImpl<I>::~CXHeaderImpl()
{
}


//////////////////////////////////////////////////////////////////////////
// IXHeader
template <class I>
int CXHeaderImpl<I>::InsertItem(IN int nIndex, LPCTSTR szText)
{
    return InsertItem(nIndex, szText, g_nDefaultDrawFlags, g_nDefaultItemMinWidth);
}

template <class I>
int CXHeaderImpl<I>::InsertItem(IN int nIndex, LPCTSTR szText, DWORD dwTextEnvFlags, int nWidth)
{
    nIndex = (nIndex > (int)m_ItemInfos.size()) ? (int)m_ItemInfos.size() : nIndex;

    if(!IntSkinImpl::CheckFlags(dwTextEnvFlags, DT_END_ELLIPSIS)
        && !IntSkinImpl::CheckFlags(dwTextEnvFlags, DT_PATH_ELLIPSIS)
        && !IntSkinImpl::CheckFlags(dwTextEnvFlags, DT_WORD_ELLIPSIS))
    {
        dwTextEnvFlags = dwTextEnvFlags | DT_END_ELLIPSIS;
    }

    ItemInfo itemInfo;
    IntSkinImpl::SetorLoadStringSaveResId(szText, itemInfo.strText, itemInfo.strTextResId);
    itemInfo.dwData = 0;
    itemInfo.dwDrawFlags = dwTextEnvFlags;
    itemInfo.nMinWidth = g_nDefaultItemMinWidth;
    itemInfo.nWidth = nWidth;
    itemInfo.dwFlags = HF_Sortable;
    m_ItemInfos.insert(m_ItemInfos.begin() + nIndex, itemInfo);

    if(nIndex <= m_nSortIndex)
        ++ m_nSortIndex;

    AutoSizeImpl(FALSE);
    Invalidate();
    return nIndex;
}

template <class I>
int CXHeaderImpl<I>::GetItemCount()
{
    return (int)m_ItemInfos.size();
}

template <class I>
int CXHeaderImpl<I>::GetItemWidth(IN int nIndex)
{
    if(nIndex >= (int)m_ItemInfos.size())
        return -1;

    return m_ItemInfos[nIndex].nWidth;
}

template <class I>
void CXHeaderImpl<I>::SetItemWidth(IN int nIndex, IN int nWidth)
{
    if(nIndex >= (int)m_ItemInfos.size())
        return;

    m_ItemInfos[nIndex].nWidth = nWidth;
    AutoSizeImpl(FALSE);
    Invalidate();
}

template <class I>
int CXHeaderImpl<I>::GetTotalWidth()
{
    int nTotalWidth = 0;
    ItemInfoContainter::iterator IteInfo = m_ItemInfos.begin();
    for(; IteInfo != m_ItemInfos.end(); ++ IteInfo)
    {
        ItemInfo& info = *IteInfo;
        nTotalWidth += info.nWidth;
    }
    return nTotalWidth;
}


template <class I>
void CXHeaderImpl<I>::SetItemData(IN int nIndex, IN DWORD dwData)
{
    XASSERT_ERROR(nIndex > 0 && nIndex < (int)m_ItemInfos.size(), _T("nIndex Out of Bounds, in IXHeader::SetItemData"));
    if(nIndex < 0 || nIndex >= (int)m_ItemInfos.size())
        return;

    m_ItemInfos[nIndex].dwData = dwData;
}

template <class I>
DWORD CXHeaderImpl<I>::GetItemData(IN int nIndex)
{
    XASSERT_ERROR(nIndex > 0 && nIndex < (int)m_ItemInfos.size(), _T("nIndex Out of Bounds, in IXHeader::GetItemData"));
    if(nIndex < 0 || nIndex >= (int)m_ItemInfos.size())
        return 0;

    return m_ItemInfos[nIndex].dwData;
}

template <class I>
void CXHeaderImpl<I>::RemoveItem(IN int nIndex)
{
    XASSERT_ERROR(nIndex > 0 && nIndex < (int)m_ItemInfos.size(), _T("nIndex Out of Bounds, in IXHeader::RemoveItem"));
    if(nIndex < 0 || nIndex >= (int)m_ItemInfos.size())
        return;

    if(m_nSortIndex == nIndex)
        m_nSortIndex = -1;

    m_ItemInfos.erase(m_ItemInfos.begin() + nIndex);
    AutoSizeImpl(FALSE);
    Invalidate();
}

template <class I>
void CXHeaderImpl<I>::RemoveAllItems()
{
    m_ItemInfos.clear();
    AutoSizeImpl(FALSE);
    Invalidate();
}

template <class I>
void CXHeaderImpl<I>::SetItemFlags(int nIndex, DWORD dwFlags)
{
    if(nIndex >= (int)m_ItemInfos.size())
        return;

    ItemInfo& info = m_ItemInfos[nIndex];
    if(info.dwFlags != dwFlags)
    {
        info.dwFlags = dwFlags;
        if(m_nSortIndex == nIndex && !IntSkinImpl::CheckFlags(dwFlags, HF_Sortable))
            m_nSortIndex = -1;
        Invalidate();
    }
}

template <class I>
DWORD CXHeaderImpl<I>::GetItemFlags(IN int nIndex)
{
    if(nIndex >= (int)m_ItemInfos.size())
        return 0;

    ItemInfo& info = m_ItemInfos[nIndex];
    return info.dwFlags;
}

template <class I>
int CXHeaderImpl<I>::GetSortIndex()
{
    return m_nSortIndex;
}

template <class I>
BOOL CXHeaderImpl<I>::GetSortAsc()
{
    return m_bSortAsc;
}
//////////////////////////////////////////////////////////////////////////
// IXFrame

template <class I>
void CXHeaderImpl<I>::GetAutoSize(OUT LPSIZE pAutoSize)
{
    int nTotalWidth = 0;
    int nMaxHeight = 0;
    SIZE sizeText = {0};

    IXFont* pFont = NULL;
    IXTextEnv* pTextEnv = NULL;

    get_Font(&pFont);
    get_TextEnv(&pTextEnv);

    ItemInfoContainter::iterator IteItem = m_ItemInfos.begin();
    for(; IteItem != m_ItemInfos.end(); ++ IteItem)
    {
        ItemInfo& info = *IteItem;
        sizeText.cx = GetWidth();
        sizeText.cy = GetHeight();
        DrawUtil::XGetTextSize(info.strText.c_str(), info.strText.Length(), &sizeText, pFont, pTextEnv);
        nTotalWidth += g_nItemBorderWidth + sizeText.cx + g_nItemBorderWidth;
        nMaxHeight = (nMaxHeight > sizeText.cy) ? nMaxHeight : sizeText.cy;
    }

    if(nMaxHeight <= 0)
    {
        sizeText.cx = 65535;
        sizeText.cy = 65535;
        DrawUtil::XGetTextSize(XDEMO_SINGLINE_TEXT, XDEMO_SINGLINE_TEXT_LENGTH, &sizeText, pFont, pTextEnv);
        nMaxHeight = sizeText.cy;
    }

    nMaxHeight += 10;

    pAutoSize->cx = nTotalWidth;
    pAutoSize->cy = nMaxHeight;
}

template <class I>
void CXHeaderImpl<I>::Draw(XCANVAS xCanvas, const RECT& rcDraw)
{
    superclass::Draw(xCanvas, rcDraw);

    IXFont* pFont = NULL;
    IXTextEnv* pTextEnv = NULL;
    get_Font(&pFont);
    get_TextEnv(&pTextEnv);

    DWORD dwFlags = pTextEnv->get_DrawFlags();

    RECT rcItem;
    get_WindowRect(&rcItem);

    DrawUtil::DrawGradient(xCanvas, rcItem, RGB(231, 239, 255), RGB(214, 231, 247), TRUE);

    // DrawUtil::FillRect(xCanvas, &rcItem, RGB(128, 128, 128));
    // DrawUtil::Rectangle(xCanvas, &rcItem, RGB(0, 0, 255));
    POINT PtSrc = {rcItem.left, rcItem.top};
    POINT PtDes = {rcItem.right, rcItem.top};
    DrawUtil::DrawLine(xCanvas, PtSrc, PtDes, RGB(214, 227, 247));
    PtSrc.y = rcItem.top + 1;
    PtDes.y = rcItem.top + 1;
    DrawUtil::DrawLine(xCanvas, PtSrc, PtDes, RGB(247, 251, 255));

    PtSrc.y = rcItem.bottom - 1;
    PtDes.y = rcItem.bottom - 1;
    DrawUtil::DrawLine(xCanvas, PtSrc, PtDes, RGB(173, 190, 222));

    BOOL bDrawTriUseImage = FALSE;
    if(m_pArrow == NULL)
    {
        if(SUCCEEDED(Util::CreateObject(&m_pArrow)))
        {
            bDrawTriUseImage = SUCCEEDED(m_pArrow->put_ImagePath(_T("@skin:Common\\arrow.png")));
            m_pArrow->put_SubCount(2, 2);
        }
    }
    else
    {
        bDrawTriUseImage = m_pArrow->IsDrawValid();
    }

    SIZE sizeTriangle;
    if(bDrawTriUseImage)
        m_pArrow->get_ImageSize(&sizeTriangle);
    else
        DrawUtil::GetTriangleSize(&sizeTriangle);

    PtSrc.y = rcItem.top + 5;
    PtDes.y = rcItem.bottom - 5;
    int nWidth = (rcItem.right - rcItem.left);
    size_t nItemCount = m_ItemInfos.size();
    for(size_t i=0; i<nItemCount; ++ i)
    {
        ItemInfo& info = m_ItemInfos[i];
        rcItem.right = rcItem.left + info.nWidth;
        if(rcItem.right > 0)    // May be scroll to left
        {
            // DrawUtil::FillRect(xCanvas, &rcItem, RGB(255, 255, 0));
            // DrawUtil::Rectangle(xCanvas, &rcItem, RGB(255, 0, 255));

            PtSrc.x = rcItem.right - 2;
            PtDes.x = rcItem.right - 2;
            DrawUtil::DrawLine(xCanvas, PtSrc, PtDes, RGB(247, 247, 255));

            PtSrc.x = rcItem.right - 1;
            PtDes.x = rcItem.right - 1;
            DrawUtil::DrawLine(xCanvas, PtSrc, PtDes, RGB(173, 190, 222));

            ::InflateRect(&rcItem, -g_nItemBorderWidth, -g_nItemBorderWidth);

            if(i == m_nSortIndex)
                rcItem.right -= (sizeTriangle.cx + g_nTriMargin * 2);

            pTextEnv->put_DrawFlags(info.dwDrawFlags);
            DrawUtil::XDrawText(xCanvas, info.strText.GetData(), info.strText.Length(), &rcItem, pTextEnv, pFont);

            if(i == m_nSortIndex)
            {
                if(bDrawTriUseImage)
                {
                    m_pArrow->put_Index(m_bSortAsc ? 0 : 1, 1);
                    RECT rcArrow = {0};
                    rcArrow.left =  rcItem.right + g_nTriMargin;
                    rcArrow.top = rcItem.top + (rcItem.bottom - rcItem.top - sizeTriangle.cy) / 2;
                    rcArrow.right = rcArrow.left + sizeTriangle.cx;
                    rcArrow.bottom = rcArrow.top + sizeTriangle.cy;
                    m_pArrow->Draw(xCanvas, &rcArrow);
                }
                else
                {
                    DrawUtil::DrawTriangle(xCanvas, rcItem.right + g_nTriMargin, rcItem.top + (rcItem.bottom - rcItem.top - sizeTriangle.cy) / 2, m_bSortAsc);
                }
                rcItem.right += (sizeTriangle.cx + g_nTriMargin * 2);
            }

            ::InflateRect(&rcItem, g_nItemBorderWidth, g_nItemBorderWidth);
        }
        rcItem.left = rcItem.right;
    }

    pTextEnv->put_DrawFlags(dwFlags);
}

template <class I>
HCURSOR CXHeaderImpl<I>::GetCursor(IN POINT* pt)
{
    HitTestCode code;
    int nIndex = HitTest(*pt, code);

    HCURSOR hCursor = NULL;
    if(code == HHC_SplitMargin)
    {
        if(m_hSplitCursor == NULL)
        {
            m_hSplitCursor = DrawObjHelper::GetCursor(MAKEINTRESOURCE(IDC_CURSOR_HORZ_SPLIT), XAppData::GetXSkinModule());
        }
        hCursor = m_hSplitCursor;
    }

    if(hCursor == NULL)
        hCursor = superclass::GetCursor(pt);
    if(hCursor == NULL)
    {
        hCursor = DrawObjHelper::GetDefaultCursor();
    }
    return hCursor;
}

template <class I>
BOOL CXHeaderImpl<I>::OnMouseDown(POINT* pt)
{
    superclass::OnMouseDown(pt);

    HitTestCode code;
    int nIndex = HitTest(*pt, code);
    m_nClickedIndex = nIndex;

    m_nDragIndex = (code == HHC_SplitMargin) ? nIndex : -1;
    m_nOrgX = pt->x;

    if(m_uWidthChangedTimerId == g_nInvalidTimerId)
        m_uWidthChangedTimerId = TimerUtil::SetTimer(this, 50, 0);

    return TRUE;
}

template <class I>
BOOL CXHeaderImpl<I>::OnMouseUp(POINT* pt)
{
    superclass::OnMouseUp(pt);

    CancelDrag();
    return TRUE;
}

template <class I>
BOOL CXHeaderImpl<I>::OnMouseMove(IN POINT* pt)
{
    superclass::OnMouseMove(pt);

    return TRUE;
}

template <class I>
BOOL CXHeaderImpl<I>::OnMouseLeave(POINT* pt)
{
    if(TestControlFlags(ESkin_Hover))
    {
        if(UIUtil::IsKeyPressed(VK_LBUTTON))
        {
        }
        else
        {
            CancelDrag();
            ModifyControlFlags(ESkin_Down, FALSE);
        }
        superclass::OnMouseLeave(pt);
    }

    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
// IXBaseObj

template <class I>
void CXHeaderImpl<I>::OnFinalRelease()
{
    superclass::OnFinalRelease();

    XSAFE_RELEASE(m_pArrow);

    CancelDrag();
    if(m_hSplitCursor != NULL)
    {
        XSkinObjMgr* pMgr = XSkinObjMgr::instance();
        if(pMgr != NULL)
        {
            pMgr->ReleaseCursor(m_hSplitCursor);
            m_hSplitCursor = NULL;
        }
    }
}

//////////////////////////////////////////////////////////////////////////
// IXTimer
template <class I>
void CXHeaderImpl<I>::OnTimer(UINT_PTR uTimerId, void* pData)
{
    if(m_uWidthChangedTimerId == uTimerId)
    {
        if(!UIUtil::IsKeyPressed(VK_LBUTTON))
        {
            CancelDrag();
        }
        else if(m_nDragIndex != -1)
        {
            POINT Pt;
            UIUtil::XGetCurrentMsgPoint(&Pt);
            ::ScreenToClient((HWND)UIUtil::GetHolderWindow(dynamic_cast<IXControl*>(this)), &Pt);

            ItemInfo& info = m_ItemInfos[m_nDragIndex];
            int nOffsetWidth = Pt.x - m_nOrgX;
            if(nOffsetWidth != 0 && info.nWidth + nOffsetWidth >= info.nMinWidth)
            {
                info.nWidth += nOffsetWidth;

                AutoSizeImpl(FALSE);

                if(!IsAutoSize())
                {
                    stHeaderWidthChangedEventInfo eventInfo = {m_nDragIndex, info.nWidth};
                    InvokeEventHandler(this, HeaderEvent_WidthChanged, (LPARAM)&eventInfo);
                    Invalidate();
                }
            }
            m_nOrgX = Pt.x;
        }
    }
}

//////////////////////////////////////////////////////////////////////////
// CXControlImpl
template <class I>
void CXHeaderImpl<I>::AutoSizeImpl(BOOL bForce)
{
    superclass::AutoSizeImpl(bForce);
    if(bForce || IsAutoSize())
    {
        SIZE sizeText = {0};

        IXFont* pFont = NULL;
        IXTextEnv* pTextEnv = NULL;

        get_Font(&pFont);
        get_TextEnv(&pTextEnv);

        int nWidth = 0;
        size_t nCount = m_ItemInfos.size();
        for(size_t i=0; i<nCount; ++ i)
        {
            ItemInfo& info = m_ItemInfos[i];
            sizeText.cx = GetWidth();
            sizeText.cy = GetHeight();
            DrawUtil::XGetTextSize(info.strText.c_str(), info.strText.Length(), &sizeText, pFont, pTextEnv);
            nWidth = g_nItemBorderWidth + sizeText.cx + g_nItemBorderWidth;

            if(i == m_nSortIndex)
            {
                SIZE size;
                DrawUtil::GetTriangleSize(&size);
                nWidth += size.cx + g_nTriMargin + g_nTriMargin;
            }

            if(nWidth != info.nWidth)
            {
                info.nWidth = nWidth;
                stHeaderWidthChangedEventInfo eventInfo = {i, info.nWidth};
                InvokeEventHandler(this, HeaderEvent_WidthChanged, (LPARAM)&eventInfo);
            }
        }
    }
}

template <class I>
BOOL CXHeaderImpl<I>::OnClicked(const POINT* pt)
{
    HitTestCode code;
    int nIndex = HitTest(*pt, code);

    if(code == HHC_Center && m_nClickedIndex == nIndex)
    {
        if(nIndex >= (int)m_ItemInfos.size())
            return TRUE;

        ItemInfo& ItemInfo = m_ItemInfos[nIndex];
        if(IntSkinImpl::CheckFlags(ItemInfo.dwFlags, HF_Sortable))
        {
            m_bSortAsc = (nIndex == m_nSortIndex) ? !m_bSortAsc : TRUE;
            m_nSortIndex = nIndex;
            Invalidate();
        }

        stHeaderClickedEventInfo info = {m_nSortIndex, m_bSortAsc};
        InvokeEventHandler(this, HeaderEvent_Clicked, (LPARAM)&info);
    }
    superclass::OnClicked(pt);
    return TRUE;
}

// CXControlImpl
template <class I>
HRESULT CXHeaderImpl<I>::OnSkinLangChanged()
{
    superclass::OnSkinLangChanged();

    BOOL bChanged = FALSE;
    XString strTemp;
    ItemInfoContainter::iterator IteItem = m_ItemInfos.begin();
    for(; IteItem != m_ItemInfos.end(); ++ IteItem)
    {
        ItemInfo& item = *IteItem;
        if(IntSkinImpl::ReLoadString(item.strTextResId.c_str(), strTemp))
        {
            item.strText = strTemp;
            bChanged = TRUE;
        }
    }
    if(bChanged)
    {
        AutoSizeImpl(FALSE);
        Invalidate();
    }
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
// CXHeaderImpl
template <class I>
int CXHeaderImpl<I>::HitTest(IN const POINT& pt, OUT HitTestCode& code)
{
    RECT rcItem;
    get_WindowRect(&rcItem);

    code = HHC_None;
    if(pt.y < rcItem.top || pt.x < rcItem.left || pt.y >= rcItem.bottom)
        return -1;

    size_t nIndex = 0;
    size_t nCount = m_ItemInfos.size();
    for(nIndex=0; nIndex<nCount; ++ nIndex)
    {
        ItemInfo& info = m_ItemInfos[nIndex];

        rcItem.right = rcItem.left + info.nWidth;

        if(pt.x >= rcItem.left && pt.x <= rcItem.right)
        {
            break;
        }

        rcItem.left = rcItem.right;
    }

    if(nIndex < nCount)
    {
        if(nIndex == 0)
        {
            if(pt.x <= rcItem.right - g_nSplitMargin)
                code = HHC_Center;
            else
                code = HHC_SplitMargin;
        }
        else
        {
            if(pt.x < rcItem.left + g_nSplitMargin)
            {
                -- nIndex;
                code = HHC_SplitMargin;
            }
            else if(pt.x >= rcItem.right - g_nSplitMargin)
            {
                code = HHC_SplitMargin;
            }
            else
            {
                code = HHC_Center;
            }
        }
        if(nIndex == -1)
            code = HHC_None;
    }
    else
    {
        code = HHC_None;
        nIndex = -1;
    }

    if(nIndex != -1 && code == HHC_SplitMargin)
    {
        DWORD dwFlags = GetItemFlags(nIndex);
        if(IntSkinImpl::CheckFlags(dwFlags, HF_Fixed))
            code = HHC_Center;
    }

    return nIndex;
}

template <class I>
void CXHeaderImpl<I>::CancelDrag()
{
    if(m_uWidthChangedTimerId != g_nInvalidTimerId)
    {
        TimerUtil::KillTimer(m_uWidthChangedTimerId);
        m_uWidthChangedTimerId = g_nInvalidTimerId;
    }

    m_nDragIndex = -1;
    m_nClickedIndex = -1;
}
